# OneToManyResolver.java

    package org.codefilarete.stalactite.engine.configurer.resolver.onetomany;

    import java.util.Collection;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.Consumer;
    import java.util.function.Function;

    import org.codefilarete.stalactite.engine.configurer.AssociationRecordMapping;
    import org.codefilarete.stalactite.engine.configurer.IndexedAssociationRecordMapping;
    import org.codefilarete.stalactite.engine.configurer.model.DirectRelationJoin;
    import org.codefilarete.stalactite.engine.configurer.model.IntermediaryRelationJoin;
    import org.codefilarete.stalactite.engine.configurer.model.ResolvedOneToManyRelation;
    import org.codefilarete.stalactite.engine.configurer.resolver.SkeletonAggregateResolver;
    import org.codefilarete.stalactite.engine.runtime.AssociationRecord;
    import org.codefilarete.stalactite.engine.runtime.AssociationRecordPersister;
    import org.codefilarete.stalactite.engine.runtime.AssociationTable;
    import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
    import org.codefilarete.stalactite.engine.runtime.IndexedAssociationRecord;
    import org.codefilarete.stalactite.engine.runtime.IndexedAssociationTable;
    import org.codefilarete.stalactite.engine.runtime.onetomany.AbstractOneToManyEngine;
    import org.codefilarete.stalactite.engine.runtime.onetomany.IndexedAssociationTableManyRelationDescriptor;
    import org.codefilarete.stalactite.engine.runtime.onetomany.IndexedMappedManyRelationDescriptor;
    import org.codefilarete.stalactite.engine.runtime.onetomany.ManyRelationDescriptor;
    import org.codefilarete.stalactite.engine.runtime.onetomany.MappedManyRelationDescriptor;
    import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithAssociationTableEngine;
    import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithIndexedAssociationTableEngine;
    import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithIndexedMappedAssociationEngine;
    import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithMappedAssociationEngine;
    import org.codefilarete.stalactite.mapping.id.assembly.IdentifierAssembler;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.KeyMapping;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.collection.Maps;

    import static org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode.ALL_ORPHAN_REMOVAL;
    import static org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode.ASSOCIATION_ONLY;
    import static org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode.READ_ONLY;

    public class OneToManyResolver {

    	private final SkeletonAggregateResolver skeletonAggregateResolver;
    	private final Dialect dialect;
    	private final ConnectionConfiguration connectionConfiguration;

    	public OneToManyResolver(SkeletonAggregateResolver skeletonAggregateResolver, Dialect dialect, ConnectionConfiguration connectionConfiguration) {
    		this.skeletonAggregateResolver = skeletonAggregateResolver;
    		this.dialect = dialect;
    		this.connectionConfiguration = connectionConfiguration;
    	}

    	/**
    	 * Appends the direct one-to-many relations to given {@link ConfiguredRelationalPersister}
    	 * @param resolvedRelation the entity to collect one-to-manys from
    	 * @param sourcePersister the persister to append one-to-many relations to
    	 * @param createdPersisterConsumer a consumer that processes the resolved one-to-many relationship along with the configured persister
    	 *                                  for the target entity after it has been created.
    	 * @param <SRC> type of the source entity
    	 * @param <SRCID> type of the source entity identifier
    	 * @param <TRGT> type of the target entity
    	 * @param <TRGTID> type of the target entity identifier
    	 * @param <S> type of the collection of target entities
    	 * @param <LEFTTABLE> type of the source entity table
    	 * @param <RIGHTTABLE> type of the target entity table
    	 * @param <JOINID> type of the join table identifier
    	 */
    	public <SRC, SRCID, TRGT, TRGTID, S extends Collection<TRGT>, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>, JOINID>
    	void resolve(ResolvedOneToManyRelation<SRC, TRGT, S, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE> resolvedRelation,
    	             ConfiguredRelationalPersister<SRC, SRCID> sourcePersister,
    	             Consumer<ConfiguredRelationalPersister<TRGT, TRGTID>> createdPersisterConsumer) {
    		ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister = skeletonAggregateResolver.buildPersister(resolvedRelation.getTargetEntity());
    		createdPersisterConsumer.accept(targetPersister);

    		AbstractOneToManyEngine<SRC, TRGT, SRCID, TRGTID, S> oneToManyEngine;
    		if (resolvedRelation.isOwnedByReverseSide()) {

    			DirectRelationJoin<LEFTTABLE, RIGHTTABLE, SRCID> join = (DirectRelationJoin<LEFTTABLE, RIGHTTABLE, SRCID>) resolvedRelation.getJoin();
    			KeyMapping<LEFTTABLE, RIGHTTABLE, SRCID> foreignKeyColumnsMapping = join.getLeftKey().reference(join.getRightKey());

    			Function<SRCID, Map<Column<RIGHTTABLE, ?>, ?>> reverseColumnsValueProvider;
    			reverseColumnsValueProvider = srcid -> {
    				IdentifierAssembler<SRCID, LEFTTABLE> identifierAssembler = sourcePersister.getMapping().getIdMapping().getIdentifierAssembler();
    				Map<Column<LEFTTABLE, ?>, ?> columnValues = identifierAssembler.getColumnValues(srcid);
    				return Maps.innerJoin(foreignKeyColumnsMapping.getMapping(), columnValues);
    			};
    			Set<Column<RIGHTTABLE, ?>> reverseColumns = join.getRightKey().getColumns();

    			if (resolvedRelation.isOrdered()) {
    				IndexedMappedManyRelationDescriptor<SRC, TRGT, S, SRCID, TRGTID, RIGHTTABLE> manyRelationDescriptor = new IndexedMappedManyRelationDescriptor<>(
    						resolvedRelation.getAccessor(),
    						resolvedRelation.getComponentFactory(),
    						resolvedRelation.getMappedByAccessor(),
    						join.getRightKey(),
    						resolvedRelation.getIndexingMappedColumn(),
    						sourcePersister.getMapping()::getId,
    						targetPersister.getMapping()::getId,
    						resolvedRelation.getRelationMode() == ASSOCIATION_ONLY,
    						resolvedRelation.getRelationMode() == ALL_ORPHAN_REMOVAL);

    				oneToManyEngine = new OneToManyWithIndexedMappedAssociationEngine<SRC, TRGT, SRCID, TRGTID, S, LEFTTABLE, RIGHTTABLE>(
    						targetPersister,
    						manyRelationDescriptor,
    						sourcePersister,
    						reverseColumns,
    						reverseColumnsValueProvider);
    			} else {
    				MappedManyRelationDescriptor<SRC, TRGT, S, SRCID, RIGHTTABLE> manyRelationDescriptor = new MappedManyRelationDescriptor<>(
    						resolvedRelation.getAccessor(),
    						resolvedRelation.getComponentFactory(),
    						resolvedRelation.getMappedByAccessor(),
    						join.getRightKey(),
    						resolvedRelation.getRelationMode() == ASSOCIATION_ONLY,
    						resolvedRelation.getRelationMode() == ALL_ORPHAN_REMOVAL);

    				oneToManyEngine = new OneToManyWithMappedAssociationEngine<>(
    						targetPersister,
    						manyRelationDescriptor,
    						sourcePersister,
    						reverseColumns,
    						reverseColumnsValueProvider);
    			}
    		} else {
    			if (resolvedRelation.isOrdered()) {
    				oneToManyEngine = buildIndexedAssociationTableEngine(sourcePersister, resolvedRelation, targetPersister);
    			} else {
    				oneToManyEngine = buildAssociationTableEngine(sourcePersister, resolvedRelation, targetPersister);
    			}
    		}

    		boolean writeAuthorized = resolvedRelation.getRelationMode() != READ_ONLY;
    		if (writeAuthorized) {
    			oneToManyEngine.addInsertCascade(targetPersister);
    			oneToManyEngine.addUpdateCascade(targetPersister);
    			oneToManyEngine.addDeleteCascade(targetPersister);
    		}
    	}

    	private <SRC, SRCID, TRGT, TRGTID, S extends Collection<TRGT>,
    			LEFTTABLE extends Table<LEFTTABLE>,
    			RIGHTTABLE extends Table<RIGHTTABLE>,
    			ASSOCIATIONTABLE extends AssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, SRCID, TRGTID>>
    	AbstractOneToManyEngine<SRC, TRGT, SRCID, TRGTID, S>
    	buildAssociationTableEngine(ConfiguredRelationalPersister<SRC, SRCID> result, ResolvedOneToManyRelation<SRC, TRGT, S, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE> resolvedRelation, ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {

    		IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, SRCID, TRGTID> join = (IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, SRCID, TRGTID>) resolvedRelation.getJoin();
    		ManyRelationDescriptor<SRC, TRGT, S> manyRelationDescriptor = new ManyRelationDescriptor<>(
    				resolvedRelation.getAccessor(),
    				resolvedRelation.getComponentFactory(),
    				resolvedRelation.getMappedByAccessor(),
    				resolvedRelation.getRelationMode() == ASSOCIATION_ONLY,
    				resolvedRelation.getRelationMode() == ALL_ORPHAN_REMOVAL);

    		AssociationRecordPersister<AssociationRecord, ASSOCIATIONTABLE> associationPersister = new AssociationRecordPersister<>(
    				new AssociationRecordMapping<>(
    						join.getJoinTable(),
    						result.getMapping().getIdMapping().getIdentifierAssembler(),
    						targetPersister.getMapping().getIdMapping().getIdentifierAssembler()),
    				dialect,
    				connectionConfiguration);

    		return new OneToManyWithAssociationTableEngine<>(
    				result,
    				targetPersister,
    				manyRelationDescriptor,
    				associationPersister,
    				dialect.getWriteOperationFactory(),
    				dialect);
    	}

    	private <SRC, SRCID, TRGT, TRGTID, S extends Collection<TRGT>,
    			LEFTTABLE extends Table<LEFTTABLE>,
    			RIGHTTABLE extends Table<RIGHTTABLE>,
    			ASSOCIATIONTABLE extends IndexedAssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, SRCID, TRGTID>>
    	AbstractOneToManyEngine<SRC, TRGT, SRCID, TRGTID, S> buildIndexedAssociationTableEngine(ConfiguredRelationalPersister<SRC, SRCID> sourcePersister,
    	                                                                                        ResolvedOneToManyRelation<SRC, TRGT, S, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE> resolvedRelation,
    	                                                                                        ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {

    		IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, SRCID, TRGTID> join = (IntermediaryRelationJoin<LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE, SRCID, TRGTID>) resolvedRelation.getJoin();
    		IndexedAssociationTableManyRelationDescriptor<SRC, TRGT, S, SRCID> manyRelationDescriptor = new IndexedAssociationTableManyRelationDescriptor<>(
    				resolvedRelation.getAccessor(),
    				resolvedRelation.getComponentFactory(),
    				resolvedRelation.getMappedByAccessor(),
    				sourcePersister.getMapping()::getId,
    				resolvedRelation.getRelationMode() == ASSOCIATION_ONLY,
    				resolvedRelation.getRelationMode() == ALL_ORPHAN_REMOVAL);

    		AssociationRecordPersister<IndexedAssociationRecord, ASSOCIATIONTABLE> indexedAssociationPersister =
    				new AssociationRecordPersister<>(
    						new IndexedAssociationRecordMapping<>(
    								join.getJoinTable(),
    								sourcePersister.getMapping().getIdMapping().getIdentifierAssembler(),
    								targetPersister.getMapping().getIdMapping().getIdentifierAssembler(),
    								join.getJoinTable().getLeftIdentifierColumnMapping(),
    								join.getJoinTable().getRightIdentifierColumnMapping()),
    						dialect,
    						connectionConfiguration);

    		return new OneToManyWithIndexedAssociationTableEngine<>(
    				sourcePersister,
    				targetPersister,
    				manyRelationDescriptor,
    				indexedAssociationPersister,
    				resolvedRelation.getIndexingAssociationColumn(),
    				dialect.getWriteOperationFactory(),
    				dialect);
    	}
    }